/////////////////////////////////////////////////////////////////////////////
// Name:        imagecache.h
// Purpose:     A class for saving thumbnails for visited directories
// Author:      Julian Smart
// Modified by:
// Created:     2006-05-11
// RCS-ID:      $$
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"

#ifdef __WXMAC__
#include "wx/mac/private.h"
#endif

#include "wx/image.h"
#include "wx/dataobj.h"
#include "wx/wfstream.h"
#include "wx/mstream.h"
#include "wx/datstrm.h"
#include "wx/filename.h"

#include "imageutils/imagecache.h"

/*
 * wxImageCacheItem: an item in a folder cache
 */

void wxImageCacheItem::MakeFromImage(const wxImage& image, const wxString& filename, const wxSize& sz)
{
    m_size = sz;
    m_filename = filename;
    m_lastUpdated = wxDateTime::Now();

    wxImage image2(image);
    bool res = m_imageBlock.MakeImageBlock(image2, wxBITMAP_TYPE_JPEG);
    wxUnusedVar(res);
    wxASSERT( res );
}

/*
 * wxImageCache: a class for maintaining a cache of folders visited by
 * an image browser
 */

IMPLEMENT_CLASS(wxImageCache, wxObject)


/// Initialize members
void wxImageCache::Init()
{
    m_maxCachedFolders = 100;
    m_modified = false;
    m_indexModified = false;
    m_indexName = wxT("imagecacheindex.idx");
    m_enabled = true;
}

/// Clear cache and index
void wxImageCache::Clear()
{
    ClearCache();
    ClearIndex();
}

/// Clear current cache
void wxImageCache::ClearCache()
{
    for (wxNode* node = m_cacheItems.GetFirst(); node; node = node->GetNext())
    {
        wxImageCacheItem* item = (wxImageCacheItem*) node->GetData();
        delete item;
    }
    m_cacheItems.Clear();
    SetModified(false);
}

/// Clear index
void wxImageCache::ClearIndex()
{
    m_indexFolders.Clear();
    m_indexPaths.Clear();
    SetIndexModified(false);
}

/// Delete all cache files
void wxImageCache::ClearAllCacheFiles()
{
    ClearCache();

    size_t i;
    for (i = 0; i < m_indexPaths.GetCount(); i++)
    {
        wxString path = GetFullCachePath(m_indexPaths[i]);
        if (wxFileExists(path))
            wxRemoveFile(path);
    }

    ClearIndex();
    SetIndexModified(true);
    SaveCacheIndex();
}

/// Loads the cache items for this folder, if any. If there are
/// no cached items, will set the current folder in case there
/// new items are added.
bool wxImageCache::LoadFolderCache(const wxString& folder, bool WXUNUSED(force))
{
    if (!IsEnabled())
        return false;

    if (IsModified())
        UpdateCache();

    ClearCache();

    SetCurrentCacheFolder(folder);

    wxString cacheFilename = GetCacheFilename();
    if (cacheFilename.IsEmpty() || !wxFileExists(cacheFilename))
        return true;

    wxFileInputStream stream(cacheFilename);
    wxDataInputStream dataStream(stream);

    int count, version;
    
    dataStream >> version;
    dataStream >> count;
    int i;
    for (i = 0; i < count; i++)
    {
        wxImageCacheItem* item = new wxImageCacheItem;

        int recordType;
        
        dataStream >> recordType;
        dataStream >> item->m_filename;
        dataStream >> item->m_size.x >> item->m_size.y;

        wxUint64 date = 0;
        dataStream >> date;
        item->m_lastUpdated.Set((time_t) date);        
        item->m_imageBlock.Read(stream);

        AddCacheItem(item);
    }
    
    return true;
}

/// Saves the current cache items for this folder, if any
bool wxImageCache::SaveFolderCache()
{
    if (!IsEnabled())
        return false;

    if (IsModified())
    {
        wxString cacheFilename = GetCacheFilename();
        if (cacheFilename.IsEmpty())
        {
            SetModified(false);
            return true;
        }

        wxFileOutputStream stream(cacheFilename);
        wxDataOutputStream dataStream(stream);

        dataStream << (int) wxIMAGE_CACHE_FILE_VERSION;
        dataStream << (int) m_cacheItems.GetCount();

        for (wxNode* node = m_cacheItems.GetFirst(); node; node = node->GetNext())
        {
            wxImageCacheItem* item = (wxImageCacheItem*) node->GetData();
            
            dataStream << wxIMAGE_CACHE_TYPE_IMAGE;
            dataStream << item->m_filename;
            dataStream << item->m_size.x << item->m_size.y;
            dataStream << (wxUint64) item->m_lastUpdated.GetTicks();

            item->m_imageBlock.Write(stream);
        }

        SetModified(false);
    }
    return true;
}

/// Loads the cache index, if any
bool wxImageCache::LoadCacheIndex(bool WXUNUSED(force))
{
    if (!IsEnabled())
        return false;

    ClearIndex();

    wxString indexFilename = GetIndexFilename();
    if (!wxFileExists(indexFilename))
        return true;

    wxFileInputStream stream(indexFilename);
    wxDataInputStream dataStream(stream);

    wxString folder, path;

    int count = 0;
    int i;

    dataStream >> count;

    for (i = 0; i < count; i ++)
    {
        if (stream.Eof())
            break;

        dataStream >> folder;
        if (!folder.IsEmpty())
        {
            dataStream >> path;

            m_indexFolders.Add(folder);
            m_indexPaths.Add(path);
        }
    }

    return true;
}

/// Saves the cache index, if necessary
bool wxImageCache::SaveCacheIndex()
{
    if (!IsEnabled())
        return false;

    wxString indexFilename = GetIndexFilename();

    wxFileOutputStream stream(indexFilename);
    wxDataOutputStream dataStream(stream);
    dataStream << (int) m_indexFolders.GetCount();
    size_t i;
    for (i = 0; i < m_indexFolders.GetCount(); i++)
    {
        dataStream << m_indexFolders[i] << m_indexPaths[i];
    }

    SetIndexModified(false);

    return true;
}

/// Adds a cache item
bool wxImageCache::AddCacheItem(wxImageCacheItem* item)
{
    if (!IsEnabled())
        return false;

    m_cacheItems.Append(item);
    SetModified(true);
    return true;
}

/// Adds a cache image
bool wxImageCache::AddCacheItem(const wxImage& image, const wxString& filename, const wxSize& sz)
{
    if (!IsEnabled())
        return false;

    wxImageCacheItem* item = new wxImageCacheItem;
    item->MakeFromImage(image, filename, sz);
    AddCacheItem(item);
    return true;
}

/// Creates a new cache, and marks the cache as modified.
/// Index won't be updated until UpdateCache is called.
bool wxImageCache::NewCache(const wxString& WXUNUSED(folder))
{
    if (IsModified() || IsIndexModified())
        UpdateCache();

    ClearCache();

    return true;
}

/// Saves the current folder cache (if modified) and index.
bool wxImageCache::UpdateCache()
{
    if (!IsEnabled())
        return false;

    if (IsModified())
    {
        if (!SaveFolderCache())
            return false;
    }

    if (IsIndexModified())
    {
        if (!SaveCacheIndex())
            return false;
    }

    SetModified(false);
    SetIndexModified(false);
    return true;
}

/// Purges the current cache of any items that are out of date w.r.t.
/// the file on disk, or are a different size from that specified.
bool wxImageCache::PurgeCache(const wxSize& sz)
{
    wxNode* node = m_cacheItems.GetFirst();
    while (node)
    {
        wxImageCacheItem* item = (wxImageCacheItem*) node->GetData();
        wxString fullPath = GetFullPath(item->m_filename);

        if ((sz != wxDefaultSize && item->m_size != sz) || !wxFileExists(fullPath)
            || (wxFileName(fullPath).GetModificationTime() > item->m_lastUpdated))
        {
            wxNode* next = node->GetNext();
            delete item;
            delete node;
            node = next;

            SetModified(true);
        }
        else
            node = node->GetNext();
    }
    return true;
}

//// Find a cache item by its filename (minus path)
wxImageCacheItem* wxImageCache::FindCacheItem(const wxString& filename)
{
    wxString toMatch(filename);
#ifdef __WXMSW__
    toMatch.MakeLower();
#endif
    for (wxNode* node = m_cacheItems.GetFirst(); node; node = node->GetNext())
    {
        wxImageCacheItem* item = (wxImageCacheItem*) node->GetData();
        wxString itemFilename(item->m_filename);
#ifdef __WXMSW__
        itemFilename.MakeLower();
#endif
        if (toMatch == itemFilename)
            return item;
    }
    return NULL;
}

//// Remove a cache item from list and delete
void wxImageCache::DeleteCacheItem(wxImageCacheItem* item)
{
    m_cacheItems.DeleteObject(item);
    delete item;
}

/// Set the cache folder as the default directory for temporary files
void wxImageCache::SetCacheStorageFolderFromTemp()
{
    m_cacheStorageFolder = GetTempDir();
}

/// Get the index filename
wxString wxImageCache::GetIndexFilename() const
{
    wxASSERT( !m_cacheStorageFolder.IsEmpty() );

    return GetCacheStorageFolder() + wxFILE_SEP_PATH + m_indexName;
}

/// Set the current cache folder
void wxImageCache::SetCurrentCacheFolder(const wxString& folder)
{
    m_currentFolder = folder;

    AddToIndex(folder);
}

/// Get full path
wxString wxImageCache::GetFullPath(const wxString& filename) const
{
    wxString f = m_currentFolder;
    if (f.Last() != wxFILE_SEP_PATH)
        f += wxFILE_SEP_PATH;
    f += filename;
    return f;
}

/// Get full path in the cache folder
wxString wxImageCache::GetFullCachePath(const wxString& filename) const
{
    wxString f = m_cacheStorageFolder;
    if (f.Last() != wxFILE_SEP_PATH)
        f += wxFILE_SEP_PATH;
    f += filename;
    return f;
}

/// Get the filename for the current folder cache
wxString wxImageCache::GetCacheFilename() const
{
    int idx = m_indexFolders.Index(m_currentFolder);
    if (idx != wxNOT_FOUND)
    {
        return GetFullCachePath(m_indexPaths[idx]);
    }
    else
        return wxEmptyString;
}

/// Make a new filename for the given folder
wxString wxImageCache::MakeNewCacheFilename(const wxString& folder) const
{
    wxString folderName(wxFileNameFromPath(folder));
    if (folderName.IsEmpty())
        folderName = wxT("top");
    int i = 1;
    wxString filename;
    do
    {
        filename.Printf(wxT("images-%s-%d.cache"), (const wxChar*) folderName, i);
        i ++;
    }
    while (wxFileExists(GetFullCachePath(filename)));
    return filename;
}

/// Add the folder to the index if necessary
void wxImageCache::AddToIndex(const wxString& folder)
{
    int idx = m_indexFolders.Index(folder);
    if (idx == wxNOT_FOUND)
    {
        wxString cacheFile = MakeNewCacheFilename(folder);
        m_indexFolders.Add(folder);
        m_indexPaths.Add(cacheFile);
        
        SetIndexModified(true);
    }
}

/// Has the file been modified since this time?
bool wxImageCache::ModifiedMoreRecently(const wxDateTime& date, const wxString& filename)
{
    return GetModificationTime(filename) > date;
}

/// Get the modification time
wxDateTime wxImageCache::GetModificationTime(const wxString& filename)
{
    return wxFileName(filename).GetModificationTime();
}

wxString wxImageCache::GetTempDir() const
{
    wxString dir;
#if defined(__WXMAC__) && !defined(__DARWIN__)
    dir = wxMacFindFolder(  (short) kOnSystemDisk, kTemporaryFolderType, kCreateFolder ) ;
#elif defined(__WXMSW__)
    if ( !::GetTempPath(MAX_PATH, wxStringBuffer(dir, MAX_PATH + 1)) )
    {
        wxLogLastError(_T("GetTempPath"));
    }
    else
    {
        if (dir.Length() > 1 && dir.Last() == wxT('\\'))
        {
            dir = dir.Left(dir.Length() - 1);
        }
    }
#else
    dir = wxGetenv(_T("TMP"));
    if ( dir.empty() )
    {
        dir = wxGetenv(_T("TEMP"));
    }
#endif
    if ( dir.empty() )
    {
        // default
#ifdef __DOS__
        dir = _T(".");
#else
        dir = _T("/tmp");
#endif
    }
    return dir;
}
